/*********************************************************************
 * Copyright (C) 2002 Andrew Khan
 * <p>
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * <p>
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * <p>
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ***************************************************************************/

package jxl.read.biff;

import jxl.*;
import jxl.biff.*;
import jxl.biff.CellReferenceHelper;
import jxl.biff.PaletteRecord;
import jxl.biff.drawing.DrawingGroup;
import jxl.biff.drawing.MsoDrawingGroupRecord;
import jxl.biff.drawing.Origin;
import jxl.biff.formula.ExternalSheet;
import jxl.common.Assert;
import jxl.common.Logger;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

/**
 * Parses the biff file passed in, and builds up an internal representation of
 * the spreadsheet
 */
public class WorkbookParser extends Workbook
        implements ExternalSheet, WorkbookMethods {
    /**
     * The logger
     */
    private static Logger logger = Logger.getLogger(WorkbookParser.class);

    /**
     * The excel file
     */
    private File excelFile;
    /**
     * The number of open bofs
     */
    private int bofs;
    /**
     * Indicates whether or not the dates are based around the 1904 date system
     */
    private boolean nineteenFour;
    /**
     * The shared string table
     */
    private SSTRecord sharedStrings;
    /**
     * The names of all the worksheets
     */
    private ArrayList boundsheets;
    /**
     * The xf records
     */
    private FormattingRecords formattingRecords;
    /**
     * The fonts used by this workbook
     */
    private Fonts fonts;

    /**
     * The sheets contained in this workbook
     */
    private ArrayList sheets;

    /**
     * The last sheet accessed
     */
    private SheetImpl lastSheet;

    /**
     * The index of the last sheet retrieved
     */
    private int lastSheetIndex;

    /**
     * The named records found in this workbook
     */
    private HashMap namedRecords;

    /**
     * The list of named records
     */
    private ArrayList nameTable;

    /**
     * The list of add in functions
     */
    private ArrayList addInFunctions;

    /**
     * The external sheet record.  Used by formulas, and names
     */
    private ExternalSheetRecord externSheet;

    /**
     * The list of supporting workbooks - used by formulas
     */
    private ArrayList supbooks;

    /**
     * The bof record for this workbook
     */
    private BOFRecord workbookBof;

    /**
     * The Mso Drawing Group record for this workbook
     */
    private MsoDrawingGroupRecord msoDrawingGroup;

    /**
     * The property set record associated with this workbook
     */
    private ButtonPropertySetRecord buttonPropertySet;

    /**
     * Workbook protected flag
     */
    private boolean wbProtected;

    /**
     * Contains macros flag
     */
    private boolean containsMacros;

    /**
     * The workbook settings
     */
    private WorkbookSettings settings;

    /**
     * The drawings contained in this workbook
     */
    private DrawingGroup drawingGroup;

    /**
     * The country record (containing the language and regional settings)
     * for this workbook
     */
    private CountryRecord countryRecord;

    private ArrayList xctRecords;

    /**
     * Constructs this object from the raw excel data
     *
     * @param f the excel 97 biff file
     * @param s the workbook settings
     */
    public WorkbookParser(File f, WorkbookSettings s) {
        super();
        excelFile = f;
        boundsheets = new ArrayList(10);
        fonts = new Fonts();
        formattingRecords = new FormattingRecords(fonts);
        sheets = new ArrayList(10);
        supbooks = new ArrayList(10);
        namedRecords = new HashMap();
        lastSheetIndex = -1;
        wbProtected = false;
        containsMacros = false;
        settings = s;
        xctRecords = new ArrayList(10);
    }

    /**
     * Gets the sheets within this workbook.
     * NOTE:  Use of this method for
     * very large worksheets can cause performance and out of memory problems.
     * Use the alternative method getSheet() to retrieve each sheet individually
     *
     * @return an array of the individual sheets
     */
    public Sheet[] getSheets() {
        Sheet[] sheetArray = new Sheet[getNumberOfSheets()];
        return (Sheet[]) sheets.toArray(sheetArray);
    }

    /**
     * Interface method from WorkbookMethods - gets the specified
     * sheet within this workbook
     *
     * @param index the zero based index of the required sheet
     * @return The sheet specified by the index
     */
    public Sheet getReadSheet(int index) {
        return getSheet(index);
    }

    /**
     * Gets the specified sheet within this workbook
     *
     * @param index the zero based index of the required sheet
     * @return The sheet specified by the index
     */
    public Sheet getSheet(int index) {
        // First see if the last sheet index is the same as this sheet index.
        // If so, then the same sheet is being re-requested, so simply
        // return it instead of rereading it
        if ((lastSheet != null) && lastSheetIndex == index) {
            return lastSheet;
        }

        // Flush out all of the cached data in the last sheet
        if (lastSheet != null) {
            lastSheet.clear();

            if (!settings.getGCDisabled()) {
                System.gc();
            }
        }

        lastSheet = (SheetImpl) sheets.get(index);
        lastSheetIndex = index;
        lastSheet.readSheet();

        return lastSheet;
    }

    /**
     * Gets the sheet with the specified name from within this workbook
     *
     * @param name the sheet name
     * @return The sheet with the specified name, or null if it is not found
     */
    public Sheet getSheet(String name) {
        // Iterate through the boundsheet records
        int pos = 0;
        boolean found = false;
        Iterator i = boundsheets.iterator();
        BoundsheetRecord br = null;

        while (i.hasNext() && !found) {
            br = (BoundsheetRecord) i.next();

            if (br.getName().equals(name)) {
                found = true;
            } else {
                pos++;
            }
        }

        return found ? getSheet(pos) : null;
    }

    /**
     * Gets the sheet names
     *
     * @return an array of strings containing the sheet names
     */
    public String[] getSheetNames() {
        String[] names = new String[boundsheets.size()];

        BoundsheetRecord br = null;
        for (int i = 0; i < names.length; i++) {
            br = (BoundsheetRecord) boundsheets.get(i);
            names[i] = br.getName();
        }

        return names;
    }


    /**
     * Package protected function which gets the real internal sheet index
     * based upon  the external sheet reference.  This is used for extern sheet
     * references  which are specified in formulas
     *
     * @param index the external sheet reference
     * @return the actual sheet index
     */
    public int getExternalSheetIndex(int index) {
        // For biff7, the whole external reference thing works differently
        // Hopefully for our purposes sheet references will all be local
        if (workbookBof.isBiff7()) {
            return index;
        }

        Assert.verify(externSheet != null);

        int firstTab = externSheet.getFirstTabIndex(index);

        return firstTab;
    }

    /**
     * Package protected function which gets the real internal sheet index
     * based upon  the external sheet reference.  This is used for extern sheet
     * references  which are specified in formulas
     *
     * @param index the external sheet reference
     * @return the actual sheet index
     */
    public int getLastExternalSheetIndex(int index) {
        // For biff7, the whole external reference thing works differently
        // Hopefully for our purposes sheet references will all be local
        if (workbookBof.isBiff7()) {
            return index;
        }

        Assert.verify(externSheet != null);

        int lastTab = externSheet.getLastTabIndex(index);

        return lastTab;
    }

    /**
     * Gets the name of the external sheet specified by the index
     *
     * @param index the external sheet index
     * @return the name of the external sheet
     */
    public String getExternalSheetName(int index) {
        // For biff7, the whole external reference thing works differently
        // Hopefully for our purposes sheet references will all be local
        if (workbookBof.isBiff7()) {
            BoundsheetRecord br = (BoundsheetRecord) boundsheets.get(index);

            return br.getName();
        }

        int supbookIndex = externSheet.getSupbookIndex(index);
        SupbookRecord sr = (SupbookRecord) supbooks.get(supbookIndex);

        int firstTab = externSheet.getFirstTabIndex(index);
        int lastTab = externSheet.getLastTabIndex(index);
        String firstTabName = "";
        String lastTabName = "";

        if (sr.getType() == SupbookRecord.INTERNAL) {
            // It's an internal reference - get the name from the boundsheets list
            if (firstTab == 65535) {
                firstTabName = "#REF";
            } else {
                BoundsheetRecord br = (BoundsheetRecord) boundsheets.get(firstTab);
                firstTabName = br.getName();
            }

            if (lastTab == 65535) {
                lastTabName = "#REF";
            } else {
                BoundsheetRecord br = (BoundsheetRecord) boundsheets.get(lastTab);
                lastTabName = br.getName();
            }

            String sheetName = (firstTab == lastTab) ? firstTabName :
                    firstTabName + ':' + lastTabName;

            // if the sheet name contains apostrophes then escape them
            sheetName = sheetName.indexOf('\'') == -1 ? sheetName :
                    StringHelper.replace(sheetName, "\'", "\'\'");


            // if the sheet name contains spaces, then enclose in quotes
            return sheetName.indexOf(' ') == -1 ? sheetName :
                    '\'' + sheetName + '\'';
        } else if (sr.getType() == SupbookRecord.EXTERNAL) {
            // External reference - get the sheet name from the supbook record
            StringBuffer sb = new StringBuffer();
            java.io.File fl = new java.io.File(sr.getFileName());
            sb.append("'");
            sb.append(fl.getAbsolutePath());
            sb.append("[");
            sb.append(fl.getName());
            sb.append("]");
            sb.append((firstTab == 65535) ? "#REF" : sr.getSheetName(firstTab));
            if (lastTab != firstTab) {
                sb.append(sr.getSheetName(lastTab));
            }
            sb.append("'");
            return sb.toString();
        }

        // An unknown supbook - return unkown
        logger.warn("Unknown Supbook 3");
        return "[UNKNOWN]";
    }

    /**
     * Gets the name of the external sheet specified by the index
     *
     * @param index the external sheet index
     * @return the name of the external sheet
     */
    public String getLastExternalSheetName(int index) {
        // For biff7, the whole external reference thing works differently
        // Hopefully for our purposes sheet references will all be local
        if (workbookBof.isBiff7()) {
            BoundsheetRecord br = (BoundsheetRecord) boundsheets.get(index);

            return br.getName();
        }

        int supbookIndex = externSheet.getSupbookIndex(index);
        SupbookRecord sr = (SupbookRecord) supbooks.get(supbookIndex);

        int lastTab = externSheet.getLastTabIndex(index);

        if (sr.getType() == SupbookRecord.INTERNAL) {
            // It's an internal reference - get the name from the boundsheets list
            if (lastTab == 65535) {
                return "#REF";
            } else {
                BoundsheetRecord br = (BoundsheetRecord) boundsheets.get(lastTab);
                return br.getName();
            }
        } else if (sr.getType() == SupbookRecord.EXTERNAL) {
            // External reference - get the sheet name from the supbook record
            StringBuffer sb = new StringBuffer();
            java.io.File fl = new java.io.File(sr.getFileName());
            sb.append("'");
            sb.append(fl.getAbsolutePath());
            sb.append("[");
            sb.append(fl.getName());
            sb.append("]");
            sb.append((lastTab == 65535) ? "#REF" : sr.getSheetName(lastTab));
            sb.append("'");
            return sb.toString();
        }

        // An unknown supbook - return unkown
        logger.warn("Unknown Supbook 4");
        return "[UNKNOWN]";
    }

    /**
     * Returns the number of sheets in this workbook
     *
     * @return the number of sheets in this workbook
     */
    public int getNumberOfSheets() {
        return sheets.size();
    }

    /**
     * Closes this workbook, and frees makes any memory allocated available
     * for garbage collection
     */
    public void close() {
        if (lastSheet != null) {
            lastSheet.clear();
        }
        excelFile.clear();

        if (!settings.getGCDisabled()) {
            System.gc();
        }
    }

    /**
     * Adds the sheet to the end of the array
     *
     * @param s the sheet to add
     */
    final void addSheet(Sheet s) {
        sheets.add(s);
    }

    /**
     * Does the hard work of building up the object graph from the excel bytes
     *
     * @exception BiffException
     * @exception PasswordException if the workbook is password protected
     */
    protected void parse() throws BiffException, PasswordException {
        Record r = null;

        BOFRecord bof = new BOFRecord(excelFile.next());
        workbookBof = bof;
        bofs++;

        if (!bof.isBiff8() && !bof.isBiff7()) {
            throw new BiffException(BiffException.unrecognizedBiffVersion);
        }

        if (!bof.isWorkbookGlobals()) {
            throw new BiffException(BiffException.expectedGlobals);
        }
        ArrayList continueRecords = new ArrayList();
        ArrayList localNames = new ArrayList();
        nameTable = new ArrayList();
        addInFunctions = new ArrayList();

        // Skip to the first worksheet
        while (bofs == 1) {
            r = excelFile.next();

            if (r.getType() == Type.SST) {
                continueRecords.clear();
                Record nextrec = excelFile.peek();
                while (nextrec.getType() == Type.CONTINUE) {
                    continueRecords.add(excelFile.next());
                    nextrec = excelFile.peek();
                }

                // cast the array
                Record[] records = new Record[continueRecords.size()];
                records = (Record[]) continueRecords.toArray(records);

                sharedStrings = new SSTRecord(r, records, settings);
            } else if (r.getType() == Type.FILEPASS) {
                throw new PasswordException();
            } else if (r.getType() == Type.NAME) {
                NameRecord nr = null;

                if (bof.isBiff8()) {
                    nr = new NameRecord(r, settings, nameTable.size());

                } else {
                    nr = new NameRecord(r, settings, nameTable.size(),
                            NameRecord.biff7);
                }

                // Add all local and global names to the name table in order to
                // preserve the indexing
                nameTable.add(nr);

                if (nr.isGlobal()) {
                    namedRecords.put(nr.getName(), nr);
                } else {
                    localNames.add(nr);
                }
            } else if (r.getType() == Type.FONT) {
                FontRecord fr = null;

                if (bof.isBiff8()) {
                    fr = new FontRecord(r, settings);
                } else {
                    fr = new FontRecord(r, settings, FontRecord.biff7);
                }
                fonts.addFont(fr);
            } else if (r.getType() == Type.PALETTE) {
                PaletteRecord palette = new PaletteRecord(r);
                formattingRecords.setPalette(palette);
            } else if (r.getType() == Type.NINETEENFOUR) {
                NineteenFourRecord nr = new NineteenFourRecord(r);
                nineteenFour = nr.is1904();
            } else if (r.getType() == Type.FORMAT) {
                FormatRecord fr = null;
                if (bof.isBiff8()) {
                    fr = new FormatRecord(r, settings, FormatRecord.biff8);
                } else {
                    fr = new FormatRecord(r, settings, FormatRecord.biff7);
                }
                try {
                    formattingRecords.addFormat(fr);
                } catch (NumFormatRecordsException e) {
                    // This should not happen.  Bomb out
                    Assert.verify(false, e.getMessage());
                }
            } else if (r.getType() == Type.XF) {
                XFRecord xfr = null;
                if (bof.isBiff8()) {
                    xfr = new XFRecord(r, settings, XFRecord.biff8);
                } else {
                    xfr = new XFRecord(r, settings, XFRecord.biff7);
                }

                try {
                    formattingRecords.addStyle(xfr);
                } catch (NumFormatRecordsException e) {
                    // This should not happen.  Bomb out
                    Assert.verify(false, e.getMessage());
                }
            } else if (r.getType() == Type.BOUNDSHEET) {
                BoundsheetRecord br = null;

                if (bof.isBiff8()) {
                    br = new BoundsheetRecord(r, settings);
                } else {
                    br = new BoundsheetRecord(r, BoundsheetRecord.biff7);
                }

                if (br.isSheet()) {
                    boundsheets.add(br);
                } else if (br.isChart() && !settings.getDrawingsDisabled()) {
                    boundsheets.add(br);
                }
            } else if (r.getType() == Type.EXTERNSHEET) {
                if (bof.isBiff8()) {
                    externSheet = new ExternalSheetRecord(r, settings);
                } else {
                    externSheet = new ExternalSheetRecord(r, settings,
                            ExternalSheetRecord.biff7);
                }
            } else if (r.getType() == Type.XCT) {
                XCTRecord xctr = new XCTRecord(r);
                xctRecords.add(xctr);
            } else if (r.getType() == Type.CODEPAGE) {
                CodepageRecord cr = new CodepageRecord(r);
                settings.setCharacterSet(cr.getCharacterSet());
            } else if (r.getType() == Type.SUPBOOK) {
                Record nextrec = excelFile.peek();
                while (nextrec.getType() == Type.CONTINUE) {
                    r.addContinueRecord(excelFile.next());
                    nextrec = excelFile.peek();
                }

                SupbookRecord sr = new SupbookRecord(r, settings);
                supbooks.add(sr);
            } else if (r.getType() == Type.EXTERNNAME) {
                ExternalNameRecord enr = new ExternalNameRecord(r, settings);

                if (enr.isAddInFunction()) {
                    addInFunctions.add(enr.getName());
                }
            } else if (r.getType() == Type.PROTECT) {
                ProtectRecord pr = new ProtectRecord(r);
                wbProtected = pr.isProtected();
            } else if (r.getType() == Type.OBJPROJ) {
                containsMacros = true;
            } else if (r.getType() == Type.COUNTRY) {
                countryRecord = new CountryRecord(r);
            } else if (r.getType() == Type.MSODRAWINGGROUP) {
                if (!settings.getDrawingsDisabled()) {
                    msoDrawingGroup = new MsoDrawingGroupRecord(r);

                    if (drawingGroup == null) {
                        drawingGroup = new DrawingGroup(Origin.READ);
                    }

                    drawingGroup.add(msoDrawingGroup);

                    Record nextrec = excelFile.peek();
                    while (nextrec.getType() == Type.CONTINUE) {
                        drawingGroup.add(excelFile.next());
                        nextrec = excelFile.peek();
                    }
                }
            } else if (r.getType() == Type.BUTTONPROPERTYSET) {
                buttonPropertySet = new ButtonPropertySetRecord(r);
            } else if (r.getType() == Type.EOF) {
                bofs--;
            } else if (r.getType() == Type.REFRESHALL) {
                RefreshAllRecord rfm = new RefreshAllRecord(r);
                settings.setRefreshAll(rfm.getRefreshAll());
            } else if (r.getType() == Type.TEMPLATE) {
                TemplateRecord rfm = new TemplateRecord(r);
                settings.setTemplate(rfm.getTemplate());
            } else if (r.getType() == Type.EXCEL9FILE) {
                Excel9FileRecord e9f = new Excel9FileRecord(r);
                settings.setExcel9File(e9f.getExcel9File());
            } else if (r.getType() == Type.WINDOWPROTECT) {
                WindowProtectedRecord winp = new WindowProtectedRecord(r);
                settings.setWindowProtected(winp.getWindowProtected());
            } else if (r.getType() == Type.HIDEOBJ) {
                HideobjRecord hobj = new HideobjRecord(r);
                settings.setHideobj(hobj.getHideMode());
            } else if (r.getType() == Type.WRITEACCESS) {
                WriteAccessRecord war = new WriteAccessRecord(r, bof.isBiff8(),
                        settings);
                settings.setWriteAccess(war.getWriteAccess());
            } else {
                // logger.info("Unsupported record type: " +
                //            Integer.toHexString(r.getCode())+"h");
            }
        }

        bof = null;
        if (excelFile.hasNext()) {
            r = excelFile.next();

            if (r.getType() == Type.BOF) {
                bof = new BOFRecord(r);
            }
        }

        // Only get sheets for which there is a corresponding Boundsheet record
        while (bof != null && getNumberOfSheets() < boundsheets.size()) {
            if (!bof.isBiff8() && !bof.isBiff7()) {
                throw new BiffException(BiffException.unrecognizedBiffVersion);
            }

            if (bof.isWorksheet()) {
                // Read the sheet in
                SheetImpl s = new SheetImpl(excelFile,
                        sharedStrings,
                        formattingRecords,
                        bof,
                        workbookBof,
                        nineteenFour,
                        this);

                BoundsheetRecord br = (BoundsheetRecord) boundsheets.get
                        (getNumberOfSheets());
                s.setName(br.getName());
                s.setHidden(br.isHidden());
                addSheet(s);
            } else if (bof.isChart()) {
                // Read the sheet in
                SheetImpl s = new SheetImpl(excelFile,
                        sharedStrings,
                        formattingRecords,
                        bof,
                        workbookBof,
                        nineteenFour,
                        this);

                BoundsheetRecord br = (BoundsheetRecord) boundsheets.get
                        (getNumberOfSheets());
                s.setName(br.getName());
                s.setHidden(br.isHidden());
                addSheet(s);
            } else {
                logger.warn("BOF is unrecognized");


                while (excelFile.hasNext() && r.getType() != Type.EOF) {
                    r = excelFile.next();
                }
            }

            // The next record will normally be a BOF or empty padding until
            // the end of the block is reached.  In exceptionally unlucky cases,
            // the last EOF  will coincide with a block division, so we have to
            // check there is more data to retrieve.
            // Thanks to liamg for spotting this
            bof = null;
            if (excelFile.hasNext()) {
                r = excelFile.next();

                if (r.getType() == Type.BOF) {
                    bof = new BOFRecord(r);
                }
            }
        }

        // Add all the local names to the specific sheets
        for (Iterator it = localNames.iterator(); it.hasNext(); ) {
            NameRecord nr = (NameRecord) it.next();

            if (nr.getBuiltInName() == null) {
                logger.warn("Usage of a local non-builtin name");
            } else if (nr.getBuiltInName() == BuiltInName.PRINT_AREA ||
                    nr.getBuiltInName() == BuiltInName.PRINT_TITLES) {
                // appears to use the internal tab number rather than the
                // external sheet index
                SheetImpl s = (SheetImpl) sheets.get(nr.getSheetRef() - 1);
                s.addLocalName(nr);
            }
        }
    }

    /**
     * Accessor for the formattingRecords, used by the WritableWorkbook
     * when creating a copy of this
     *
     * @return the formatting records
     */
    public FormattingRecords getFormattingRecords() {
        return formattingRecords;
    }

    /**
     * Accessor for the externSheet, used by the WritableWorkbook
     * when creating a copy of this
     *
     * @return the external sheet record
     */
    public ExternalSheetRecord getExternalSheetRecord() {
        return externSheet;
    }

    /**
     * Accessor for the MsoDrawingGroup, used by the WritableWorkbook
     * when creating a copy of this
     *
     * @return the Mso Drawing Group record
     */
    public MsoDrawingGroupRecord getMsoDrawingGroupRecord() {
        return msoDrawingGroup;
    }

    /**
     * Accessor for the supbook records, used by the WritableWorkbook
     * when creating a copy of this
     *
     * @return the supbook records
     */
    public SupbookRecord[] getSupbookRecords() {
        SupbookRecord[] sr = new SupbookRecord[supbooks.size()];
        return (SupbookRecord[]) supbooks.toArray(sr);
    }

    /**
     * Accessor for the name records.  Used by the WritableWorkbook when
     * creating a copy of this
     *
     * @return the array of names
     */
    public NameRecord[] getNameRecords() {
        NameRecord[] na = new NameRecord[nameTable.size()];
        return (NameRecord[]) nameTable.toArray(na);
    }

    /**
     * Accessor for the fonts, used by the WritableWorkbook
     * when creating a copy of this
     * @return the fonts used in this workbook
     */
    public Fonts getFonts() {
        return fonts;
    }

    /**
     * Returns the cell for the specified location eg. "Sheet1!A4".
     * This is identical to using the CellReferenceHelper with its
     * associated performance overheads, consequently it should
     * be use sparingly
     *
     * @param loc the cell to retrieve
     * @return the cell at the specified location
     */
    public Cell getCell(String loc) {
        Sheet s = getSheet(CellReferenceHelper.getSheet(loc));
        return s.getCell(loc);
    }

    /**
     * Gets the named cell from this workbook.  If the name refers to a
     * range of cells, then the cell on the top left is returned.  If
     * the name cannot be found, null is returned
     *
     * @param  name the name of the cell/range to search for
     * @return the cell in the top left of the range if found, NULL
     *         otherwise
     */
    public Cell findCellByName(String name) {
        NameRecord nr = (NameRecord) namedRecords.get(name);

        if (nr == null) {
            return null;
        }

        NameRecord.NameRange[] ranges = nr.getRanges();

        // Go and retrieve the first cell in the first range
        Sheet s = getSheet(getExternalSheetIndex(ranges[0].getExternalSheet()));
        int col = ranges[0].getFirstColumn();
        int row = ranges[0].getFirstRow();

        // If the sheet boundaries fall short of the named cell, then return
        // an empty cell to stop an exception being thrown
        if (col > s.getColumns() || row > s.getRows()) {
            return new EmptyCell(col, row);
        }

        Cell cell = s.getCell(col, row);

        return cell;
    }

    /**
     * Gets the named range from this workbook.  The Range object returns
     * contains all the cells from the top left to the bottom right
     * of the range.
     * If the named range comprises an adjacent range,
     * the Range[] will contain one object; for non-adjacent
     * ranges, it is necessary to return an array of length greater than
     * one.
     * If the named range contains a single cell, the top left and
     * bottom right cell will be the same cell
     *
     * @param name the name to find
     * @return the range of cells
     */
    public Range[] findByName(String name) {
        NameRecord nr = (NameRecord) namedRecords.get(name);

        if (nr == null) {
            return null;
        }

        NameRecord.NameRange[] ranges = nr.getRanges();

        Range[] cellRanges = new Range[ranges.length];

        for (int i = 0; i < ranges.length; i++) {
            cellRanges[i] = new RangeImpl
                    (this,
                            getExternalSheetIndex(ranges[i].getExternalSheet()),
                            ranges[i].getFirstColumn(),
                            ranges[i].getFirstRow(),
                            getLastExternalSheetIndex(ranges[i].getExternalSheet()),
                            ranges[i].getLastColumn(),
                            ranges[i].getLastRow());
        }

        return cellRanges;
    }

    /**
     * Gets the named ranges
     *
     * @return the list of named cells within the workbook
     */
    public String[] getRangeNames() {
        Object[] keys = namedRecords.keySet().toArray();
        String[] names = new String[keys.length];
        System.arraycopy(keys, 0, names, 0, keys.length);

        return names;
    }

    /**
     * Method used when parsing formulas to make sure we are trying
     * to parse a supported biff version
     *
     * @return the BOF record
     */
    public BOFRecord getWorkbookBof() {
        return workbookBof;
    }

    /**
     * Determines whether the sheet is protected
     *
     * @return whether or not the sheet is protected
     */
    public boolean isProtected() {
        return wbProtected;
    }

    /**
     * Accessor for the settings
     *
     * @return the workbook settings
     */
    public WorkbookSettings getSettings() {
        return settings;
    }

    /**
     * Accessor/implementation method for the external sheet reference
     *
     * @param sheetName the sheet name to look for
     * @return the external sheet index
     */
    public int getExternalSheetIndex(String sheetName) {
        return 0;
    }

    /**
     * Accessor/implementation method for the external sheet reference
     *
     * @param sheetName the sheet name to look for
     * @return the external sheet index
     */
    public int getLastExternalSheetIndex(String sheetName) {
        return 0;
    }

    /**
     * Gets the name at the specified index
     *
     * @param index the index into the name table
     * @return the name of the cell
     * @exception NameRangeException
     */
    public String getName(int index) throws NameRangeException {
        //    Assert.verify(index >= 0 && index < nameTable.size());
        if (index < 0 || index >= nameTable.size()) {
            throw new NameRangeException();
        }
        return ((NameRecord) nameTable.get(index)).getName();
    }

    /**
     * Gets the index of the name record for the name
     *
     * @param name the name to search for
     * @return the index in the name table
     */
    public int getNameIndex(String name) {
        NameRecord nr = (NameRecord) namedRecords.get(name);

        return nr != null ? nr.getIndex() : 0;
    }

    /**
     * Accessor for the drawing group
     *
     * @return the drawing group
     */
    public DrawingGroup getDrawingGroup() {
        return drawingGroup;
    }

    /**
     * Accessor for the CompoundFile.  For this feature to return non-null
     * value, the propertySets feature in WorkbookSettings must be enabled
     * and the workbook must contain additional property sets.  This
     * method is used during the workbook copy
     *
     * @return the base compound file if it contains additional data items
     *         and property sets are enabled
     */
    public CompoundFile getCompoundFile() {
        return excelFile.getCompoundFile();
    }

    /**
     * Accessor for the containsMacros
     *
     * @return TRUE if this workbook contains macros, FALSE otherwise
     */
    public boolean containsMacros() {
        return containsMacros;
    }

    /**
     * Accessor for the button property set, used during copying
     *
     * @return the button property set
     */
    public ButtonPropertySetRecord getButtonPropertySet() {
        return buttonPropertySet;
    }

    /**
     * Accessor for the country record, using during copying
     *
     * @return the country record read in
     */
    public CountryRecord getCountryRecord() {
        return countryRecord;
    }

    /**
     * Accessor for addin function names
     *
     * @return list of add in function names
     */
    public String[] getAddInFunctionNames() {
        String[] addins = new String[0];
        return (String[]) addInFunctions.toArray(addins);
    }

    /**
     * Gets the sheet index in this workbook.  Used when importing a sheet
     *
     * @param sheet the sheet
     * @return the 0-based sheet index, or -1 if it is not found
     */
    public int getIndex(Sheet sheet) {
        String name = sheet.getName();
        int index = -1;
        int pos = 0;

        for (Iterator i = boundsheets.iterator(); i.hasNext() && index == -1; ) {
            BoundsheetRecord br = (BoundsheetRecord) i.next();

            if (br.getName().equals(name)) {
                index = pos;
            } else {
                pos++;
            }
        }

        return index;
    }

    public XCTRecord[] getXCTRecords() {
        XCTRecord[] xctr = new XCTRecord[0];
        return (XCTRecord[]) xctRecords.toArray(xctr);
    }
}







